package com.d3works.apps.bankpayment.service;

import com.d3works.apps.account.db.InvoicePayment;
import com.d3works.apps.account.db.InvoiceTerm;
import com.d3works.apps.account.db.Move;
import com.d3works.apps.account.db.PaymentMode;
import com.d3works.apps.account.db.PaymentSession;
import com.d3works.apps.account.db.repo.InvoicePaymentRepository;
import com.d3works.apps.account.db.repo.InvoiceTermRepository;
import com.d3works.apps.account.db.repo.MoveRepository;
import com.d3works.apps.account.db.repo.PaymentModeRepository;
import com.d3works.apps.account.db.repo.PaymentSessionRepository;
import com.d3works.apps.account.service.PaymentSessionValidateServiceImpl;
import com.d3works.apps.account.service.ReconcileService;
import com.d3works.apps.account.service.app.AppAccountService;
import com.d3works.apps.account.service.config.AccountConfigService;
import com.d3works.apps.account.service.invoice.InvoiceTermService;
import com.d3works.apps.account.service.move.MoveCreateService;
import com.d3works.apps.account.service.move.MoveValidateService;
import com.d3works.apps.account.service.moveline.MoveLineCreateService;
import com.d3works.apps.account.service.moveline.MoveLineTaxService;
import com.d3works.apps.account.service.payment.PaymentModeService;
import com.d3works.apps.account.service.payment.invoice.payment.InvoicePaymentCreateService;
import com.d3works.apps.account.service.payment.invoice.payment.InvoicePaymentValidateService;
import com.d3works.apps.bankpayment.db.BankOrder;
import com.d3works.apps.bankpayment.db.BankOrderFileFormat;
import com.d3works.apps.bankpayment.db.BankOrderLine;
import com.d3works.apps.bankpayment.db.repo.BankOrderRepository;
import com.d3works.apps.bankpayment.exception.IExceptionMessage;
import com.d3works.apps.bankpayment.service.bankorder.BankOrderCreateService;
import com.d3works.apps.bankpayment.service.bankorder.BankOrderLineOriginService;
import com.d3works.apps.bankpayment.service.bankorder.BankOrderLineService;
import com.d3works.apps.bankpayment.service.bankorder.BankOrderService;
import com.d3works.apps.base.db.Partner;
import com.d3works.apps.base.db.repo.PartnerRepository;
import com.d3works.apps.base.service.CurrencyService;
import com.d3works.apps.base.service.PartnerService;
import com.d3works.apps.base.service.app.AppBaseService;
import com.d3works.common.StringUtils;
import com.d3works.db.JPA;
import com.d3works.exception.D3worksException;
import com.d3works.exception.db.repo.TraceBackRepository;
import com.d3works.i18n.I18n;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.persistence.TypedQuery;
import javax.xml.bind.JAXBException;
import javax.xml.datatype.DatatypeConfigurationException;

public class PaymentSessionValidateBankPaymentServiceImpl
    extends PaymentSessionValidateServiceImpl {
  protected BankOrderService bankOrderService;
  protected BankOrderCreateService bankOrderCreateService;
  protected BankOrderLineService bankOrderLineService;
  protected BankOrderLineOriginService bankOrderLineOriginService;
  protected BankOrderRepository bankOrderRepo;
  protected CurrencyService currencyService;
  protected AppAccountService appAccountService;

  @Inject
  public PaymentSessionValidateBankPaymentServiceImpl(
      AppBaseService appBaseService,
      MoveCreateService moveCreateService,
      MoveValidateService moveValidateService,
      MoveLineCreateService moveLineCreateService,
      ReconcileService reconcileService,
      InvoiceTermService invoiceTermService,
      MoveLineTaxService moveLineTaxService,
      InvoicePaymentCreateService invoicePaymentCreateService,
      InvoicePaymentValidateService invoicePaymentValidateService,
      PaymentSessionRepository paymentSessionRepo,
      InvoiceTermRepository invoiceTermRepo,
      MoveRepository moveRepo,
      PartnerRepository partnerRepo,
      AccountConfigService accountConfigService,
      PartnerService partnerService,
      PaymentModeService paymentModeService,
      BankOrderService bankOrderService,
      BankOrderCreateService bankOrderCreateService,
      BankOrderLineService bankOrderLineService,
      BankOrderLineOriginService bankOrderLineOriginService,
      BankOrderRepository bankOrderRepo,
      CurrencyService currencyService,
      AppAccountService appAccountService,
      InvoicePaymentRepository invoicePaymentRepo) {
    super(
        appBaseService,
        moveCreateService,
        moveValidateService,
        moveLineCreateService,
        reconcileService,
        invoiceTermService,
        moveLineTaxService,
        invoicePaymentCreateService,
        invoicePaymentValidateService,
        paymentSessionRepo,
        invoiceTermRepo,
        moveRepo,
        partnerRepo,
        invoicePaymentRepo,
        accountConfigService,
        partnerService,
        paymentModeService);
    this.bankOrderService = bankOrderService;
    this.bankOrderCreateService = bankOrderCreateService;
    this.bankOrderLineService = bankOrderLineService;
    this.bankOrderLineOriginService = bankOrderLineOriginService;
    this.bankOrderRepo = bankOrderRepo;
    this.currencyService = currencyService;
    this.appAccountService = appAccountService;
  }

  @Override
  @Transactional(rollbackOn = {Exception.class})
  public int processPaymentSession(PaymentSession paymentSession) throws D3worksException {
    if (paymentSession.getPaymentMode() != null
        && paymentSession.getPaymentMode().getGenerateBankOrder()
        && paymentSession.getBankOrder() == null) {
      this.generateBankOrderFromPaymentSession(paymentSession);
    }

    return super.processPaymentSession(paymentSession);
  }

  @Override
  protected void postProcessPaymentSession(
      PaymentSession paymentSession,
      Map<LocalDate, Map<Partner, List<Move>>> moveDateMap,
      Map<Move, BigDecimal> paymentAmountMap,
      boolean out,
      boolean isGlobal)
      throws D3worksException {
    if (paymentSession.getBankOrder() != null) {
      BankOrder bankOrder = bankOrderRepo.find(paymentSession.getBankOrder().getId());
      bankOrderService.updateTotalAmounts(bankOrder);
      bankOrderRepo.save(bankOrder);

      if (paymentSession.getPaymentMode().getAutoConfirmBankOrder()
          && bankOrder.getStatusSelect() == BankOrderRepository.STATUS_DRAFT) {
        try {
          bankOrderService.confirm(bankOrder);
        } catch (JAXBException | IOException | DatatypeConfigurationException e) {
          throw new D3worksException(
              TraceBackRepository.CATEGORY_INCONSISTENCY, e.getLocalizedMessage());
        }
      }
    }

    super.postProcessPaymentSession(paymentSession, moveDateMap, paymentAmountMap, out, isGlobal);
  }

  @Transactional(rollbackOn = {Exception.class})
  protected BankOrder generateBankOrderFromPaymentSession(PaymentSession paymentSession)
      throws D3worksException {
    BankOrder bankOrder = this.createBankOrder(paymentSession);

    paymentSession.setBankOrder(bankOrder);
    bankOrderService.generateSequence(bankOrder);

    return bankOrder;
  }

  protected BankOrder createBankOrder(PaymentSession paymentSession) throws D3worksException {
    BankOrder bankOrder =
        bankOrderCreateService.createBankOrder(
            paymentSession.getPaymentMode(),
            paymentSession.getPartnerTypeSelect(),
            paymentSession.getPaymentDate(),
            paymentSession.getCompany(),
            paymentSession.getBankDetails(),
            paymentSession.getCurrency(),
            paymentSession.getSequence(),
            this.getLabel(paymentSession),
            BankOrderRepository.TECHNICAL_ORIGIN_AUTOMATIC,
            BankOrderRepository.FUNCTIONAL_ORIGIN_PAYMENT_SESSION,
            paymentSession.getAccountingTriggerSelect());

    bankOrder.setIsMultiDate(this.isMultiDate(paymentSession));

    if (!paymentSession.getCurrency().equals(paymentSession.getCompany().getCurrency())) {
      bankOrder.setIsMultiCurrency(true);
    }

    return bankOrder;
  }

  protected boolean isMultiDate(PaymentSession paymentSession) {
    return Optional.of(paymentSession)
            .map(PaymentSession::getPaymentMode)
            .map(PaymentMode::getBankOrderFileFormat)
            .map(BankOrderFileFormat::getIsMultiDate)
            .orElse(false)
        && paymentSession.getMoveAccountingDateSelect()
            == PaymentSessionRepository.MOVE_ACCOUNTING_DATE_ORIGIN_DOCUMENT;
  }

  @Transactional(rollbackOn = {Exception.class})
  protected PaymentSession processInvoiceTerm(
      PaymentSession paymentSession,
      InvoiceTerm invoiceTerm,
      Map<LocalDate, Map<Partner, List<Move>>> moveDateMap,
      Map<Move, BigDecimal> paymentAmountMap,
      boolean out,
      boolean isGlobal)
      throws D3worksException {
    paymentSession =
        super.processInvoiceTerm(
            paymentSession, invoiceTerm, moveDateMap, paymentAmountMap, out, isGlobal);
    if (paymentSession.getBankOrder() != null
        && paymentSession.getStatusSelect() != PaymentSessionRepository.STATUS_AWAITING_PAYMENT) {
      this.createOrUpdateBankOrderLineFromInvoiceTerm(
          paymentSession, invoiceTerm, paymentSession.getBankOrder());
    }

    return paymentSession;
  }

  @Override
  protected boolean generatePaymentsFirst(PaymentSession paymentSession) {
    return super.generatePaymentsFirst(paymentSession)
        || (paymentSession.getStatusSelect() == PaymentSessionRepository.STATUS_ONGOING
            && paymentSession.getPaymentMode().getGenerateBankOrder()
            && paymentSession.getAccountingTriggerSelect()
                != PaymentModeRepository.ACCOUNTING_TRIGGER_IMMEDIATE);
  }

  @Override
  @Transactional(rollbackOn = {Exception.class})
  protected InvoicePayment generatePendingPaymentFromInvoiceTerm(
      PaymentSession paymentSession, InvoiceTerm invoiceTerm) {
    InvoicePayment invoicePayment =
        super.generatePendingPaymentFromInvoiceTerm(paymentSession, invoiceTerm);
    if (invoicePayment == null) {
      return null;
    }
    invoicePayment.setBankOrder(paymentSession.getBankOrder());

    return invoicePaymentRepo.save(invoicePayment);
  }

  protected void createOrUpdateBankOrderLineFromInvoiceTerm(
      PaymentSession paymentSession, InvoiceTerm invoiceTerm, BankOrder bankOrder)
      throws D3worksException {
    BankOrderLine bankOrderLine = null;

    if (paymentSession.getPaymentMode().getConsoBankOrderLinePerPartner()) {
      bankOrderLine =
          bankOrder.getBankOrderLineList().stream()
              .filter(
                  it ->
                      (it.getBankOrderDate() == null
                              || it.getBankOrderDate().equals(invoiceTerm.getDueDate()))
                          && it.getPartner().equals(invoiceTerm.getMoveLine().getPartner())
                          && ((it.getReceiverBankDetails() == null
                                  && invoiceTerm.getBankDetails() == null)
                              || (it.getReceiverBankDetails() != null
                                  && it.getReceiverBankDetails()
                                      .equals(invoiceTerm.getBankDetails()))))
              .findFirst()
              .orElse(null);
    }

    if (bankOrderLine == null) {
      this.generateBankOrderLineFromInvoiceTerm(paymentSession, invoiceTerm, bankOrder);
    } else {
      this.updateBankOrderLine(paymentSession, invoiceTerm, bankOrderLine);
    }

    bankOrderRepo.save(paymentSession.getBankOrder());
  }

  protected void generateBankOrderLineFromInvoiceTerm(
      PaymentSession paymentSession, InvoiceTerm invoiceTerm, BankOrder bankOrder)
      throws D3worksException {
    LocalDate bankOrderDate = this.isMultiDate(paymentSession) ? invoiceTerm.getDueDate() : null;

    BankOrderLine bankOrderLine =
        bankOrderLineService.createBankOrderLine(
            bankOrder.getBankOrderFileFormat(),
            null,
            invoiceTerm.getMoveLine().getPartner(),
            invoiceTerm.getBankDetails(),
            invoiceTerm.getAmountPaid(),
            paymentSession.getCurrency(),
            bankOrderDate,
            this.getReference(invoiceTerm),
            this.getLabel(paymentSession),
            invoiceTerm);

    bankOrder.addBankOrderLineListItem(bankOrderLine);
    bankOrderLine.setCompanyCurrencyAmount(
        this.getAmountPaidInCompanyCurrency(paymentSession, invoiceTerm, bankOrderLine));
  }

  protected void updateBankOrderLine(
      PaymentSession paymentSession, InvoiceTerm invoiceTerm, BankOrderLine bankOrderLine)
      throws D3worksException {
    this.updateReference(invoiceTerm, bankOrderLine);
    bankOrderLine.setBankOrderAmount(
        bankOrderLine.getBankOrderAmount().add(invoiceTerm.getAmountPaid()));
    bankOrderLine.setCompanyCurrencyAmount(
        bankOrderLine
            .getCompanyCurrencyAmount()
            .add(this.getAmountPaidInCompanyCurrency(paymentSession, invoiceTerm, bankOrderLine)));
    bankOrderLine.addBankOrderLineOriginListItem(
        bankOrderLineOriginService.createBankOrderLineOrigin(invoiceTerm));
  }

  protected String getLabel(PaymentSession paymentSession) {
    return String.format(
        "%s - %s",
        paymentSession.getPaymentMode().getName(), paymentSession.getCompany().getName());
  }

  protected String getReference(InvoiceTerm invoiceTerm) {
    if (StringUtils.isEmpty(invoiceTerm.getMoveLine().getOrigin())) {
      return null;
    }
    return String.format(
        "%s (%s)",
        invoiceTerm.getMoveLine().getOrigin(),
        invoiceTerm.getDueDate().format(DateTimeFormatter.ofPattern("dd/MM/yyyy")));
  }

  protected void updateReference(InvoiceTerm invoiceTerm, BankOrderLine bankOrderLine) {
    String newReference =
        String.format(
            "%s/%s", bankOrderLine.getReceiverReference(), this.getReference(invoiceTerm));

    if (newReference.length() < 256) {
      bankOrderLine.setReceiverReference(newReference);
    }
  }

  protected BigDecimal getAmountPaidInCompanyCurrency(
      PaymentSession paymentSession, InvoiceTerm invoiceTerm, BankOrderLine bankOrderLine)
      throws D3worksException {
    return bankOrderLine.getBankOrder().getIsMultiCurrency()
        ? currencyService
            .getAmountCurrencyConvertedAtDate(
                paymentSession.getCurrency(),
                paymentSession.getCompany().getCurrency(),
                invoiceTerm.getAmountPaid(),
                bankOrderLine.getBankOrderDate())
            .setScale(AppBaseService.DEFAULT_NB_DECIMAL_DIGITS, RoundingMode.HALF_UP)
        : invoiceTerm.getAmountPaid();
  }

  public StringBuilder generateFlashMessage(PaymentSession paymentSession, int moveCount) {
    StringBuilder flashMessage = super.generateFlashMessage(paymentSession, moveCount);

    if (paymentSession.getBankOrder() != null) {
      flashMessage.append(
          String.format(
              I18n.get(IExceptionMessage.PAYMENT_SESSION_GENERATED_BANK_ORDER),
              paymentSession.getBankOrder().getBankOrderSeq()));
    }

    return flashMessage;
  }

  @Override
  public List<Partner> getPartnersWithNegativeAmount(PaymentSession paymentSession)
      throws D3worksException {
    TypedQuery<Partner> partnerQuery =
        JPA.em()
            .createQuery(
                "SELECT DISTINCT Partner FROM Partner Partner "
                    + " FULL JOIN MoveLine MoveLine on Partner.id = MoveLine.partner "
                    + " FULL JOIN InvoiceTerm InvoiceTerm on  MoveLine.id = InvoiceTerm.moveLine "
                    + " WHERE InvoiceTerm.paymentSession = :paymentSession "
                    + " AND InvoiceTerm.isSelectedOnPaymentSession = true "
                    + " GROUP BY Partner.id , InvoiceTerm.bankDetails "
                    + " HAVING SUM(InvoiceTerm.paymentAmount) < 0 ",
                Partner.class);

    partnerQuery.setParameter("paymentSession", paymentSession);

    return partnerQuery.getResultList();
  }
}
